package com.eii.excel.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * Excel 工具类
 */
public class ExcelUtils {

    private static Logger log = LoggerFactory.getLogger(ExcelUtils.class);

    public final static HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy();

    /**
     * 导出Excel sheet最大行数，默认1047576
     */
    public static final int sheetSize = SpreadsheetVersion.EXCEL2007.getLastRowIndex() - 1000;

    /**
     * 读取列表 MultipartFile
     *
     * @param file MultipartFile文件
     * @param head Excel head 头
     * @param <T>  泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        return EasyExcel.read(file.getInputStream(), head, null)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }

    /**
     * 将列表以 Excel流 响应给前端
     * response[ContentType:application/octet-stream]
     *
     * @param response 响应
     * @param filename 文件名
     * @param head     Excel head 头
     * @param datas    数据列表
     * @param <T>      泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void exportExcelStream(HttpServletResponse response, String filename, Class<T> head, List<T> datas) throws IOException {
        log.info("exportExcel outputStream ===>>> fileName:{}, data size:{}", filename, datas.size());
        // 设置 header 和 contentType
        DownloadUtil.setDownloadProp(response, filename);
        // 输出 Excel
        writeToResponse(head, datas, response.getOutputStream());
    }

    /**
     * 将列表以 Excel file 响应给前端
     * response[ContentType:application/octet-stream]
     *
     * @param response 响应
     * @param filename 文件名
     * @param head     Excel head 头
     * @param datas    数据列表
     * @param <T>      泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void exportExcelFile(HttpServletResponse response, String filename,
                                           Class<T> head, List<T> datas) throws IOException {
        log.info("exportExcel file ===>>> fileName:{}, data size:{}", filename, datas.size());
        // 设置 header 和 contentType
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        // 输出 Excel
        writeToResponse(head, datas, response.getOutputStream());
    }

    private static <T> void writeToResponse(Class<T> head, List<T> datas, ServletOutputStream outputStream) {
        ExcelWriter excelWriter = EasyExcel
                .write(outputStream, head)
                .autoCloseStream(false)
                .excelType(ExcelTypeEnum.XLSX)
//                .registerWriteHandler(horizontalCellStyleStrategy)
                .build();
        try {
            batchExcelHandle(datas, sheetSize, (i, data) -> {
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "sheet" + String.format("%02d", i)).build();
                excelWriter.write(data, writeSheet);
            });
        } finally {
            excelWriter.finish();
        }
    }

    public static <T> void batchExcelHandle(List<T> datas, int batchSize, BiConsumer<Integer, List<T>> biConsumer) {
        if (datas == null || datas.isEmpty()) {
            datas = new ArrayList<>(0);
            biConsumer.accept(0, datas);
            return;
        }
        int batchCount = (datas.size() + batchSize - 1) / batchSize;
        for (int i = 0; i < batchCount; i++) {
            List<T> data = datas.stream().skip(i * batchSize).limit(batchSize).collect(Collectors.toList());
            biConsumer.accept(i, data);
        }
    }

    private static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        WriteFont writeFont = new WriteFont();
        writeFont.setFontHeightInPoints((short) 12);
        writeFont.setFontName("Microsoft YaHei UI");
        headWriteCellStyle.setWriteFont(writeFont);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setWriteFont(writeFont);
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
        return horizontalCellStyleStrategy;
    }
}
